﻿using System.ComponentModel;
using System.Windows.Forms;
using System.Drawing;
using System;

namespace DotNet.Common
{
    /// <summary>
    /// 星座图
    /// </summary>
    public partial class ConstellationDiagram : Control
    {
        private StarPoints _starPoints;
        private double[] _iData, _qData;
        private Point _zeroPoint;
        private int _axisRadius;
        private int _valueRadius;

        #region 属性
        private double _baseValue;
        public double BaseValue
        {
            get { return _baseValue; }
            set
            {
                _baseValue = value >= 0 ? value : -value;
            }
        }

        private Font _axisFont;
        public Font AxisFont
        {
            get { return _axisFont; }
            set 
            {
                _axisFont = value;
                ComputeRadius();
            }
        }

        private Pen _axisPen;
        public Pen AxisPen
        {
            get { return _axisPen; }
            set { _axisPen = value; }
        }

        private Brush _axisBrush;
        public Brush AxisBrush
        {
            get { return _axisBrush; }
            set { _axisBrush = value; }
        }
        private string _xLabel;
        public string XLabel
        {
            get { return _xLabel; }
            set { _xLabel = value; }
        }

        private string _yLabel;
        public string YLabel
        {
            get { return _yLabel; }
            set { _yLabel = value; }
        }

        private Brush _pointBrush;
        public Brush PointBrush
        {
            get { return _pointBrush; }
            set { _pointBrush = value; }
        }
        #endregion

        public ConstellationDiagram()
        {
            InitializeComponent();
            InitializeCustom();
        }

        public ConstellationDiagram(IContainer container)
        {
            container.Add(this);

            InitializeComponent();
            InitializeCustom();
        }

        private void InitializeCustom()
        {
            this.BackColor = SystemColors.WindowFrame;
            this.TabStop = false;

            _starPoints = new StarPoints();
            _pointBrush = Brushes.Blue;

            this.BaseValue = 1;
            _axisFont = new Font("Arial", 9);
            _axisPen = Pens.White;
            _axisBrush = SystemBrushes.Info;
            _xLabel = "I";
            _yLabel = "Q";
        }

        public void UpdateData(double[] iData,double[] qData)
        {
            _iData = iData;
            _qData = qData;

            UpdateShow();
        }

        protected override void OnPaint(PaintEventArgs pe)
        {
            base.OnPaint(pe);
            PaintAxis(pe.Graphics);
            PaintStar(pe.Graphics);
        }

        protected override void OnSizeChanged(EventArgs e)
        {
            if (this.Width != this.Height)
            {
                if (this.Width > this.Height)
                {
                    this.Width = this.Height;
                }
                else
                {
                    this.Height = this.Width;
                }
            }
            if (this.Width < _axisFont.Height * 3 + 5)
                return;

            _zeroPoint = new Point((this.Width - 1) >> 1, (this.Height - 1) >> 1);
            ComputeRadius();

            base.OnSizeChanged(e);
            UpdateShow();
        }

        private void ComputeRadius()
        {
            _axisRadius = (_zeroPoint.X - Math.Max(GetStringWidth(_xLabel, _axisFont), _axisFont.Height));
            _valueRadius = (int)(_axisRadius * 0.70710678);
        }

        private void UpdateShow()
        {
            _starPoints.Clear();
            if (_iData != null && _qData != null)
            {
                Point p = Point.Empty;
                int x,y;
                int length = Math.Min(_iData.Length, _qData.Length);
                for (int i = 0; i < length;i++)
                {
                    if(ComputePointLocation(_iData[i],_qData[i],out x,out y))
                    {
                        p.X = x;
                        p.Y = y;
                        _starPoints.Add(p);
                    }
                }
            }

            this.Refresh();
        }

        private bool ComputePointLocation(double i, double q, out int x, out int y)
        {
            if (i < -_baseValue || i > _baseValue || q < -_baseValue || q > _baseValue)
            {
                x = 0; y = 0;
                return false;
            }

            x = (int)(_zeroPoint.X + i / _baseValue * _valueRadius);
            y = (int)(_zeroPoint.Y - q / _baseValue * _valueRadius);
            return true;
        }

        private void PaintAxis(Graphics g)
        {
            string _baseMark = _baseValue.ToString();

            // 原点
            g.DrawString("0", _axisFont, _axisBrush, _zeroPoint.X, _zeroPoint.Y);

            // X轴
            g.DrawLine(_axisPen, _zeroPoint.X - _axisRadius, _zeroPoint.Y, _zeroPoint.X + _axisRadius, _zeroPoint.Y);
            // X标签
            g.DrawString(_xLabel, _axisFont, _axisBrush, _zeroPoint.X + _axisRadius, _zeroPoint.Y - (_axisFont.Height >> 1));
            // X轴刻度
            g.DrawLine(_axisPen, _zeroPoint.X - _valueRadius, _zeroPoint.Y, _zeroPoint.X - _valueRadius, _zeroPoint.Y - 4);
            g.DrawLine(_axisPen, _zeroPoint.X + _valueRadius, _zeroPoint.Y, _zeroPoint.X + _valueRadius, _zeroPoint.Y - 4);
            // X轴刻度值
            g.DrawString(_baseMark, _axisFont, _axisBrush, _zeroPoint.X + _valueRadius - (GetStringWidth(g, _baseMark, _axisFont) >> 1), _zeroPoint.Y);

            // Y轴
            g.DrawLine(_axisPen, _zeroPoint.X, _zeroPoint.Y - _axisRadius, _zeroPoint.Y, _zeroPoint.Y + _axisRadius);
            // Y标签
            g.DrawString(_yLabel, _axisFont, _axisBrush, _zeroPoint.X - (GetStringWidth(g, _yLabel, _axisFont) >> 1), _zeroPoint.Y - _axisRadius - _axisFont.Height);
            // Y轴刻度
            g.DrawLine(_axisPen, _zeroPoint.X, _zeroPoint.Y - _valueRadius, _zeroPoint.X + 4, _zeroPoint.Y - _valueRadius);
            g.DrawLine(_axisPen, _zeroPoint.X, _zeroPoint.Y + _valueRadius, _zeroPoint.X + 4, _zeroPoint.Y + _valueRadius);
            // Y轴刻度值
            g.DrawString(_baseMark, _axisFont, _axisBrush, _zeroPoint.X - GetStringWidth(g, _baseMark, _axisFont), _zeroPoint.Y - _valueRadius - (_axisFont.Height >> 1));

            // 基准圆
            g.DrawEllipse(_axisPen, _zeroPoint.X - _axisRadius, _zeroPoint.Y - _axisRadius, _axisRadius << 1, _axisRadius << 1);
        }

        private void PaintStar(Graphics g)
        {
            Point[] points = _starPoints.Points;
            foreach (Point p in points)
            {
                g.FillRectangle(_pointBrush, p.X - 1, p.Y - 1, 3, 3);
            }
        }

        private int GetStringWidth(Graphics g, string str, Font font)
        {
            return (int)Math.Round(g.MeasureString(str, font).Width);
        }

        private int GetStringWidth(string str, Font font)
        {
            using (Graphics g = Graphics.FromHwnd(this.Handle))
            {
                return GetStringWidth(g, str, font);
            }
        }
    }
}
